/*
 * Created on 29 Dec 2007
 *
 * Copyright (c) 2004-2007 Paul John Leonard
 * 
 * http://www.frinika.com
 * 
 * This file is part of Frinika.
 * 
 * Frinika is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.

 * Frinika is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with Frinika; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package uk.ac.bath.audio;

import uk.org.toot.audio.core.AudioBuffer;
import uk.org.toot.audio.core.AudioProcess;
/**
 *
 * Implementation of a cyclic buffer.
 *
 * This buffer is fed using the in.processAudio(buff);
 *   this returns OVERFLOW if the buffer is full
 *
 * You read the buffer using out.processAudio(buff);
 *   this will block if no data is ready
 *
 *
 * @author pjl
 */
public class CycliclyBufferedAudio {

	public final static int OVERFLOW = -1;

	float buff[];

	int cacheSize;

	long inPtr = 0;

	long outPtr = 0;

	Thread outBlockingThread = null;

	public final AudioProcess in = new In();

	public final AudioProcess out = new Out();

	private int required;

	private boolean itWasMe = false;

	// private boolean disconnected = false;

	int overflowCount=0;

	private boolean blocking=false;
	
	public CycliclyBufferedAudio(int cacheSize) {
		this.cacheSize = cacheSize;
		buff = new float[cacheSize];
	}

	class In implements AudioProcess {

		int cnt;

		public void close() throws Exception {
			// TODO Auto-generated method stub

		}

		public void open() throws Exception {
			// TODO Auto-generated method stub

		}

		public int processAudio(AudioBuffer buffer) {

			int n = buffer.getSampleCount();

			if (inPtr + n - outPtr > cacheSize) {
				System.out.println(" OVERFLOW " + overflowCount++);
				return OVERFLOW;
			}

	//		float a[] = buffer.getChannel(0);
	//		for (int i = 0; i < n; i++) {
	//			a[i] = (float) Math.sin(cnt++ * 2500.0 / 44100.0);
	//		}

			int inCy0 = (int) (inPtr % cacheSize);
			int inCy1 = (int) ((inPtr + n) % cacheSize);

			if (inCy1 > inCy0) {
				System.arraycopy(buffer.getChannel(0), 0, buff, inCy0, n);
			} else {
				System.arraycopy(buffer.getChannel(0), 0, buff, inCy0, n
						- inCy1);
				System.arraycopy(buffer.getChannel(0), n - inCy1, buff, 0,
						inCy1);
			}

			inPtr += n;

			if (blocking) { 
				if (inPtr - outPtr > required) {
					itWasMe = true;
					outBlockingThread.interrupt();   // Ha ha this can be null !!!!
				}
			}
			return AUDIO_OK;
		}
	}

	class Out implements AudioProcess {

		synchronized public int processAudio(AudioBuffer buffer) {

			required = buffer.getSampleCount();
			
			if (inPtr - outPtr < required) {
				
				outBlockingThread = Thread.currentThread();
				try {
					// clear any pending interupts
					while(Thread.currentThread().interrupted());
					blocking=true;
					wait();
				} catch (InterruptedException e) {
			
					blocking=false;
					if (itWasMe) {
				//		outBlockingThread = null;
						itWasMe = false;
					} else {
						e.printStackTrace();
						e.getCause().printStackTrace();
		//				outBlockingThread = null;
						inPtr=0;
						outPtr=0;
						itWasMe=false;
						return AudioFlags.INTERRUPTED;
					}

					// System.out.println(" Buffer unblocked ");
				}
				assert (inPtr - outPtr >= required);
			}

			int outCy0 = (int) (outPtr % cacheSize);
			int outCy1 = (int) ((outPtr + required) % cacheSize);

			if (outCy1 > outCy0) {
				System.arraycopy(buff, outCy0, buffer.getChannel(0), 0,
						required);
				// buffer.getChannel(0), 0, buff, inCy0, n);
			} else {
				System.arraycopy(buff, outCy0, buffer.getChannel(0), 0,
						required - outCy1);
				System.arraycopy(buff, 0, buffer.getChannel(0), required
						- outCy1, outCy1);

				//				
				// System.arraycopy(buffer.getChannel(0), 0, buff, inCy0,
				// n-inCy1);
				// System.arraycopy(buffer.getChannel(0), n-inCy1, buff, 0,
				// inCy1);
			}

			outPtr += required;

			return AUDIO_OK;

		}

		public void close() throws Exception {
			// TODO Auto-generated method stub

		}

		public void open() throws Exception {
			// TODO Auto-generated method stub

		}

	}

	class OverflowException extends Exception {
        @Override
		public String toString() {
			return " BufferedAuioProcess:  Overflow ";
		}
	}

	public int getLag() {
		return (int) (inPtr-outPtr);
	}
}
